#define _POSIX_C_SOURCE 200809L
#include "server.h"
#include "util.h"
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <libinput.h>

#include <wlr/types/wlr_xdg_output_v1.h>
#include <wlr/types/wlr_server_decoration.h>
#include <wlr/types/wlr_xdg_decoration_v1.h>
#include <wlr/types/wlr_presentation_time.h>
#include <wlr/types/wlr_data_device.h>
#include <wlr/backend/libinput.h>

#include "monitor.h"
#include "keyboard.h"

/* configuration, allows nested code to access above variables */
#include "config.h"
/* You can choose between:
LIBINPUT_CONFIG_SCROLL_NO_SCROLL
LIBINPUT_CONFIG_SCROLL_2FG
LIBINPUT_CONFIG_SCROLL_EDGE
LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN
*/
static const enum libinput_config_scroll_method scroll_method = LIBINPUT_CONFIG_SCROLL_2FG;
/* You can choose between:
LIBINPUT_CONFIG_SEND_EVENTS_ENABLED
LIBINPUT_CONFIG_SEND_EVENTS_DISABLED
LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE
*/
static const uint32_t send_events_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED;
/* You can choose between:
LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT
LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE
*/
static const enum libinput_config_accel_profile accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE;
static const double accel_speed = 0.0;

static void printstatus(void);

static void urgent(struct wl_listener *listener, void *data)
{
#if 0
    struct wlr_xdg_activation_v1_request_activate_event *event = data;
    Client *c = client_from_wlr_surface(event->surface);
    if (c != selclient()) {
        c->isurgent = 1;
        printstatus();
    }
#endif
}

static void updatemons(struct wl_listener *listener, void *data)
{
#if 0
    /*
     * Called whenever the output layout changes: adding or removing a
     * monitor, changing an output's mode or position, etc.  This is where
     * the change officially happens and we update geometry, window
     * positions, focus, and the stored configuration in wlroots'
     * output-manager implementation.
     */
    struct wlr_output_configuration_v1 *config =
        wlr_output_configuration_v1_create();
    Monitor *m;
    sgeom = *wlr_output_layout_get_box(output_layout, NULL);
    wl_list_for_each(m, &mons, link) {
        struct wlr_output_configuration_head_v1 *config_head =
            wlr_output_configuration_head_v1_create(config, m->wlr_output);

        /* TODO: move clients off disabled monitors */
        /* TODO: move focus if selmon is disabled */

        /* Get the effective monitor geometry to use for surfaces */
        m->m = m->w = *wlr_output_layout_get_box(output_layout, m->wlr_output);
        wlr_scene_output_set_position(m->scene_output, m->m.x, m->m.y);
        /* Calculate the effective monitor geometry to use for clients */
        arrangelayers(m);
        /* Don't move clients to the left output when plugging monitors */
        arrange(m);

        config_head->state.enabled = m->wlr_output->enabled;
        config_head->state.mode = m->wlr_output->current_mode;
        config_head->state.x = m->m.x;
        config_head->state.y = m->m.y;
    }

    wlr_output_manager_v1_set_configuration(output_mgr, config);
#endif
}

static void createmon(struct wl_listener *listener, void *data)
{
    Server *server =
        wl_container_of(listener, server, new_output);
    /* This event is raised by the backend when a new output (aka a display or
     * monitor) becomes available. */
    struct wlr_output *wlr_output = data;
    Monitor *m = wlr_output->data = monitor_create(server, wlr_output);
}

static void createidleinhibitor(struct wl_listener *listener, void *data)
{
#if 0
    struct wlr_idle_inhibitor_v1 *idle_inhibitor = data;
    wl_signal_add(&idle_inhibitor->events.destroy, &idle_inhibitor_destroy);

    wlr_idle_set_enabled(idle, seat, 0);
#endif
}

void
createlayersurface(struct wl_listener *listener, void *data)
{
#if 0
    struct wlr_layer_surface_v1 *wlr_layer_surface = data;
    LayerSurface *layersurface;
    Monitor *m;
    struct wlr_layer_surface_v1_state old_state;

    if (!wlr_layer_surface->output) {
        wlr_layer_surface->output = selmon->wlr_output;
    }

    layersurface = ecalloc(1, sizeof(LayerSurface));
    layersurface->type = LayerShell;
    LISTEN(&wlr_layer_surface->surface->events.commit,
        &layersurface->surface_commit, commitlayersurfacenotify);
    LISTEN(&wlr_layer_surface->events.destroy, &layersurface->destroy,
            destroylayersurfacenotify);
    LISTEN(&wlr_layer_surface->events.map, &layersurface->map,
            maplayersurfacenotify);
    LISTEN(&wlr_layer_surface->events.unmap, &layersurface->unmap,
            unmaplayersurfacenotify);

    layersurface->layer_surface = wlr_layer_surface;
    wlr_layer_surface->data = layersurface;
    m = wlr_layer_surface->output->data;

    layersurface->scene = wlr_layer_surface->surface->data =
            wlr_scene_subsurface_tree_create(layers[wlr_layer_surface->pending.layer],
            wlr_layer_surface->surface);
    layersurface->scene->data = layersurface;

    wl_list_insert(&m->layers[wlr_layer_surface->pending.layer],
            &layersurface->link);

    /* Temporarily set the layer's current state to pending
     * so that we can easily arrange it
     */
    old_state = wlr_layer_surface->current;
    wlr_layer_surface->current = wlr_layer_surface->pending;
    arrangelayers(m);
    wlr_layer_surface->current = old_state;
#endif
}

static void
createkeyboard(struct wlr_input_device *device, Server* server)
{
    Keyboard *kb = keyboard_create(device, server);
    device->data = kb;

    /* Here we set up listeners for keyboard events. */
    wl_signal_add(&device->keyboard->events.modifiers, &kb->modifiers);
    wl_signal_add(&device->keyboard->events.key, &kb->key);
    wl_signal_add(&device->events.destroy, &kb->destroy);

    wlr_seat_set_keyboard(server->seat->seat, device);

    /* And add the keyboard to our list of keyboards */
    wl_list_insert(&server->keyboard_manager->keyboards, &kb->link);
}

#if 1
void
createpointer(struct wlr_input_device *device, Server* server)
{
    if (wlr_input_device_is_libinput(device)) {
        struct libinput_device *libinput_device =  (struct libinput_device*)
            wlr_libinput_get_device_handle(device);

        if (libinput_device_config_tap_get_finger_count(libinput_device)) {
            libinput_device_config_tap_set_enabled(libinput_device, tap_to_click);
            libinput_device_config_tap_set_drag_enabled(libinput_device, tap_and_drag);
            libinput_device_config_tap_set_drag_lock_enabled(libinput_device, drag_lock);
        }

        if (libinput_device_config_scroll_has_natural_scroll(libinput_device))
            libinput_device_config_scroll_set_natural_scroll_enabled(libinput_device, natural_scrolling);

        if (libinput_device_config_dwt_is_available(libinput_device))
            libinput_device_config_dwt_set_enabled(libinput_device, disable_while_typing);

        if (libinput_device_config_left_handed_is_available(libinput_device))
            libinput_device_config_left_handed_set(libinput_device, left_handed);

        if (libinput_device_config_middle_emulation_is_available(libinput_device))
            libinput_device_config_middle_emulation_set_enabled(libinput_device, middle_button_emulation);

        if (libinput_device_config_scroll_get_methods(libinput_device) != LIBINPUT_CONFIG_SCROLL_NO_SCROLL)
            libinput_device_config_scroll_set_method (libinput_device, scroll_method);

        if (libinput_device_config_send_events_get_modes(libinput_device))
            libinput_device_config_send_events_set_mode(libinput_device, send_events_mode);

        if (libinput_device_config_accel_is_available(libinput_device)) {
            libinput_device_config_accel_set_profile(libinput_device, accel_profile);
            libinput_device_config_accel_set_speed(libinput_device, accel_speed);
        }
    }

    wlr_cursor_attach_input_device(server->cursor->cursor, device);
}
#endif

void
inputdevice(struct wl_listener *listener, void *data)
{
    struct Server *server =
        wl_container_of(listener, server, new_input);
    /* This event is raised by the backend when a new input device becomes
     * available. */
    struct wlr_input_device *device = data;
    uint32_t caps;

    switch (device->type) {
    case WLR_INPUT_DEVICE_KEYBOARD:
        createkeyboard(device, server);
        break;
    case WLR_INPUT_DEVICE_POINTER:
        createpointer(device, server);
        break;
    default:
        /* TODO handle other input device types */
        break;
    }

    /* We need to let the wlr_seat know what our capabilities are, which is
     * communiciated to the client. In dwl we always have a cursor, even if
     * there are no pointer devices, so we always include that capability. */
    /* TODO do we actually require a cursor? */
    caps = WL_SEAT_CAPABILITY_POINTER;
    if (!wl_list_empty(&server->keyboard_manager->keyboards))
        caps |= WL_SEAT_CAPABILITY_KEYBOARD;
    wlr_seat_set_capabilities(server->seat->seat, caps);
}

static void outputmgrapply(struct wl_listener *listener, void *data)
{
#if 0
    struct wlr_output_configuration_v1 *config = data;
    outputmgrapplyortest(config, 0);
#endif
}

void
outputmgrtest(struct wl_listener *listener, void *data)
{
#if 0
    struct wlr_output_configuration_v1 *config = data;
    outputmgrapplyortest(config, 1);
#endif
}

static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test)
{
#if 0
    /*
     * Called when a client such as wlr-randr requests a change in output
     * configuration.  This is only one way that the layout can be changed,
     * so any Monitor information should be updated by updatemons() after an
     * output_layout.change event, not here.
     */
    struct wlr_output_configuration_head_v1 *config_head;
    int ok = 1;

    wl_list_for_each(config_head, &config->heads, link) {
        struct wlr_output *wlr_output = config_head->state.output;

        wlr_output_enable(wlr_output, config_head->state.enabled);
        if (config_head->state.enabled) {
            if (config_head->state.mode)
                wlr_output_set_mode(wlr_output, config_head->state.mode);
            else
                wlr_output_set_custom_mode(wlr_output,
                        config_head->state.custom_mode.width,
                        config_head->state.custom_mode.height,
                        config_head->state.custom_mode.refresh);

            wlr_output_layout_move(output_layout, wlr_output,
                    config_head->state.x, config_head->state.y);
            wlr_output_set_transform(wlr_output, config_head->state.transform);
            wlr_output_set_scale(wlr_output, config_head->state.scale);
        }

        if (!(ok = wlr_output_test(wlr_output)))
            break;
    }
    wl_list_for_each(config_head, &config->heads, link) {
        if (ok && !test)
            wlr_output_commit(config_head->state.output);
        else
            wlr_output_rollback(config_head->state.output);
    }
    if (ok)
        wlr_output_configuration_v1_send_succeeded(config);
    else
        wlr_output_configuration_v1_send_failed(config);
    wlr_output_configuration_v1_destroy(config);
#endif
}

static void input_method_relay_init(struct input_method_relay *relay) {
    wl_list_init(&relay->text_inputs);

#if 0
    server->t
    LISTEN(&text_input_manager->events.text_input, &relay->text_input_new,
            relay_handle_text_input);

    LISTEN(&input_method_manager->events.input_method, &relay->input_method_new,
            relay_handle_input_method);
#endif
}

Server *server_create(void)
{
    Server* server = ecalloc(1, sizeof(*server));

    /* The Wayland display is managed by libwayland. It handles accepting
     * clients from the Unix socket, manging Wayland globals, and so on. */
    server->dpy = wl_display_create();

    /* The backend is a wlroots feature which abstracts the underlying input and
     * output hardware. The autocreate option will choose the most suitable
     * backend based on the current environment, such as opening an X11 window
     * if an X11 server is running. The NULL argument here optionally allows you
     * to pass in a custom renderer if wlr_renderer doesn't meet your needs. The
     * backend uses the renderer, for example, to fall back to software cursors
     * if the backend does not support hardware cursors (some older GPUs
     * don't). */
    if (!(server->backend = wlr_backend_autocreate(server->dpy))) {
        die("couldn't create backend");
    }

    /* Initialize the scene graph used to lay out windows */
    server->scene = wlr_scene_create();
    server->layers[LyrBg] = &wlr_scene_tree_create(&server->scene->node)->node;
    server->layers[LyrBottom] = &wlr_scene_tree_create(&server->scene->node)->node;
    server->layers[LyrTile] = &wlr_scene_tree_create(&server->scene->node)->node;
    server->layers[LyrFloat] = &wlr_scene_tree_create(&server->scene->node)->node;
    server->layers[LyrTop] = &wlr_scene_tree_create(&server->scene->node)->node;
    server->layers[LyrOverlay] = &wlr_scene_tree_create(&server->scene->node)->node;
    server->layers[LyrNoFocus] = &wlr_scene_tree_create(&server->scene->node)->node;
    server->layers[LyrInputPopup] = &wlr_scene_tree_create(&server->scene->node)->node;

    /* Create a renderer with the default implementation */
    if (!(server->drw = wlr_renderer_autocreate(server->backend)))
        die("couldn't create renderer");
    wlr_renderer_init_wl_display(server->drw, server->dpy);

    /* Create a default allocator */
    if (!(server->alloc = wlr_allocator_autocreate(server->backend, server->drw)))
        die("couldn't create allocator");

    /* This creates some hands-off wlroots interfaces. The compositor is
     * necessary for clients to allocate surfaces and the data device manager
     * handles the clipboard. Each of these wlroots interfaces has room for you
     * to dig your fingers in and play with their behavior if you want. Note that
     * the clients cannot set the selection directly without compositor approval,
     * see the setsel() function. */
    server->compositor = wlr_compositor_create(server->dpy, server->drw);
//    wlr_export_dmabuf_manager_v1_create(server->dpy);
//    wlr_screencopy_manager_v1_create(server->dpy);
//    wlr_data_control_manager_v1_create(server->dpy);
    wlr_data_device_manager_create(server->dpy);
//    wlr_gamma_control_manager_v1_create(server->dpy);
//    wlr_primary_selection_v1_device_manager_create(server->dpy);
//    wlr_viewporter_create(server->dpy);

    /* Initializes the interface used to implement urgency hints */
    server->activation = wlr_xdg_activation_v1_create(server->dpy);
    wl_signal_add(&server->activation->events.request_activate, &server->request_activate);
    server->request_activate.notify = urgent;

    /* Creates an output layout, which a wlroots utility for working with an
     * arrangement of screens in a physical layout. */
    server->output_layout = wlr_output_layout_create();
    wl_signal_add(&server->output_layout->events.change, &server->layout_change);
    server->layout_change.notify = updatemons;
    wlr_xdg_output_manager_v1_create(server->dpy, server->output_layout);

    /* Configure a listener to be notified when new outputs are available on the
     * backend. */
    wl_list_init(&server->mons);
    wl_signal_add(&server->backend->events.new_output, &server->new_output);
    server->new_output.notify = createmon;

    /* Set up our client lists and the xdg-shell. The xdg-shell is a
     * Wayland protocol which is used for application windows. For more
     * detail on shells, refer to the article:
     *
     * https://drewdevault.com/2018/07/29/Wayland-shells.html
     */
    wl_list_init(&clients);
    wl_list_init(&fstack);

    server->idle = wlr_idle_create(server->dpy);

    server->idle_inhibit_mgr = wlr_idle_inhibit_v1_create(server->dpy);
    wl_signal_add(&server->idle_inhibit_mgr->events.new_inhibitor, &server->idle_inhibitor_create);
    server->idle_inhibitor_create.notify = createidleinhibitor;

    server->layer_shell = wlr_layer_shell_v1_create(server->dpy);
    wl_signal_add(&server->layer_shell->events.new_surface, &server->new_layer_shell_surface);
    server->new_layer_shell_surface.notify = createlayersurface;

    server->xdg_shell = xdg_shell_create(server);
    server->xdg_decoration = xdg_decortaion_create(server);

    server->input_inhibit_mgr = wlr_input_inhibit_manager_create(server->dpy);

    /* Use decoration protocols to negotiate server-side decorations */
    wlr_server_decoration_manager_set_default_mode(
            wlr_server_decoration_manager_create(server->dpy),
            WLR_SERVER_DECORATION_MANAGER_MODE_SERVER);
    wlr_xdg_decoration_manager_v1_create(server->dpy);

    server->cursor = cursor_create(server);
    server->keyboard_manager = keyboard_manager_create(server);

    wl_signal_add(&server->backend->events.new_input, &server->new_input);
    server->new_input.notify = inputdevice;

    server->seat = seat_create(server);

    server->output_mgr = wlr_output_manager_v1_create(server->dpy);
    wl_signal_add(&server->output_mgr->events.apply, &server->output_mgr_apply);
    server->output_mgr_apply.notify = outputmgrapply;
    wl_signal_add(&server->output_mgr->events.test, &server->output_mgr_test);
    server->output_mgr_test.notify = outputmgrtest;

    wlr_scene_set_presentation(server->scene, wlr_presentation_create(server->dpy, server->backend));

    /* create text_input-, and input_method-protocol relevant globals */
//    server->input_method_manager = wlr_input_method_manager_v2_create(server->dpy);
//    server->text_input_manager = wlr_text_input_manager_v3_create(server->dpy);

    server->input_relay = calloc(1, sizeof(*server->input_relay));
    input_method_relay_init(server->input_relay);

    return server;
}

void server_free(Server *server)
{
    seat_destroy(server->seat);
    keyboard_manager_destroy(server->keyboard_manager);
    cursor_destroy(server->cursor);
    xdg_shell_destroy(server->xdg_shell);
    wl_display_destroy_clients(server->dpy);
    free(server->scene);
//    wlr_scene_node_destroy
    wlr_backend_destroy(server->backend);
    wl_display_destroy(server->dpy);
    free(server);
}

void printstatus(void)
{
#if 0
    Monitor *m = NULL;
    Client *c;
    unsigned int occ, urg, sel;

    wl_list_for_each(m, &mons, link) {
        occ = urg = 0;
        wl_list_for_each(c, &clients, link) {
            if (c->mon != m)
                continue;
            occ |= c->tags;
            if (c->isurgent)
                urg |= c->tags;
        }
        if ((c = focustop(m))) {
            printf("%s title %s\n", m->wlr_output->name, client_get_title(c));
            printf("%s fullscreen %u\n", m->wlr_output->name, c->isfullscreen);
            printf("%s floating %u\n", m->wlr_output->name, c->isfloating);
            sel = c->tags;
        } else {
            printf("%s title \n", m->wlr_output->name);
            printf("%s fullscreen \n", m->wlr_output->name);
            printf("%s floating \n", m->wlr_output->name);
            sel = 0;
        }

        printf("%s selmon %u\n", m->wlr_output->name, m == selmon);
        printf("%s tags %u %u %u %u\n", m->wlr_output->name, occ, m->tagset[m->seltags],
                sel, urg);
        printf("%s layout %s\n", m->wlr_output->name, m->lt[m->sellt]->symbol);
    }
    fflush(stdout);
#endif
}

void server_run(Server *server, const char *startup_cmd)
{
    pid_t startup_pid = -1;

    /* Add a Unix socket to the Wayland display. */
    const char *socket = wl_display_add_socket_auto(server->dpy);
    if (!socket)
        die("startup: display_add_socket_auto");
    setenv("WAYLAND_DISPLAY", socket, 1);

    /* Now that the socket exists, run the startup command */
    if (startup_cmd) {
        int piperw[2];
        if (pipe(piperw) < 0)
            die("startup: pipe:");
        if ((startup_pid = fork()) < 0)
            die("startup: fork:");
        if (startup_pid == 0) {
            dup2(piperw[0], STDIN_FILENO);
            close(piperw[0]);
            close(piperw[1]);
            execl("/bin/sh", "/bin/sh", "-c", startup_cmd, NULL);
            die("startup: execl:");
        }
        dup2(piperw[1], STDOUT_FILENO);
        close(piperw[1]);
        close(piperw[0]);
    }
    /* If nobody is reading the status output, don't terminate */
    signal(SIGPIPE, SIG_IGN);
    printstatus();

    /* Start the backend. This will enumerate outputs and inputs, become the DRM
     * master, etc */
    if (!wlr_backend_start(server->backend))
        die("startup: backend_start");

    /* Now that outputs are initialized, choose initial selmon based on
     * cursor position, and set default cursor image */
    server->selmon = xytomon(server->cursor->cursor->x, server->cursor->cursor->y, server->output_layout);

    /* TODO hack to get cursor to display in its initial location (100, 100)
     * instead of (0, 0) and then jumping.  still may not be fully
     * initialized, as the image/coordinates are not transformed for the
     * monitor when displayed here */
    wlr_cursor_warp_closest(server->cursor->cursor, NULL, server->cursor->cursor->x, server->cursor->cursor->y);
    wlr_xcursor_manager_set_cursor_image(server->cursor->cursor_mgr, "left_ptr", server->cursor->cursor);

    /* Run the Wayland event loop. This does not return until you exit the
     * compositor. Starting the backend rigged up all of the necessary event
     * loop configuration to listen to libinput events, DRM events, generate
     * frame events at the refresh rate, and so on. */
    wl_display_run(server->dpy);

    if (startup_cmd) {
        kill(startup_pid, SIGTERM);
        waitpid(startup_pid, NULL, 0);
    }
}

void server_quit(Server *server)
{
    wl_display_terminate(server->dpy);
}
